#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;

const int N = 100010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct Edge {
	int a, b, w; // a, b 为一条边的两个点，w为该边的权重 
	
	bool operator< (const Edge &W)const { //重载操作符 < 
		return w < W.w;
	}
}edges[M];

int find(int x) { //并查集核心操作 
	if (p[x] != x) p[x] = find(p[x]); //路径压缩（即递归操作 将除了祖宗结点的每个点的p[x]（即x点的父节点）都为祖宗结点） 
	return p[x]; //返回p[x]（即x点的父节点）,即祖宗结点（因为经过路径压缩过后的每个点的p[x]都为祖宗结点） 
}

int kruskal() {
	sort(edges, edges + m); //先将所有的边按权重大小sort排序（即从小到大排序）
	
	for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
	
	int res = 0, cnt = 0; // res即为最小生成树的边长总和， cnt即为走过的边数
	for (int i = 0; i < m; i ++ ) { //遍历m次操作 
		int a = edges[i].a, b = edges[i].b, w = edges[i].w; // 遍历每次操作的a, b, w（即两点和权重） 
		 
		
		a = find(a), b = find(b); //进行并查集操作 
		if (a != b) { //如果a != b (即不为自环) 
			p[a] = b; //则将两个点的连通块放到一个并查集中 
			res += w; //更新res，即将该边的权重加到res里 
			cnt ++ ; //边数+1 
		} 
	} 
	
	if (cnt < n - 1) return INF; //如果边数不为 n-1 条边的话 即没有最小生成树，故返回INF（即无穷大） 
	return res; //如果有最小生成树，则返回最后的边长总和 
}

int main() {
	scanf("%d%d", &n, &m); //输入点数n, 操作次数m（即输入两个点和权重的次数） 
	
	for (int i = 0; i < m; i ++ ) { // m次操作
		int a, b, w;  
		scanf("%d%d%d", &a, &b, &w); //输入两个点和权重
		edges[i] = {a, b, w}; //放到结构体数组中 
	}
	
	int t = kruskal();
	
	if (t == INF) puts("impossible");
	else printf("%d\n", t);
	
	return 0;
}